package com.moonLight.weblog.admin.service.Impl;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.Lists;
import com.moonLight.weblog.admin.convert.ArticleDetailConvert;
import com.moonLight.weblog.admin.convert.BlogSettingsConvert;
import com.moonLight.weblog.admin.convert.PublishArticleReqSettingsConvert;
import com.moonLight.weblog.admin.event.DeleteArticleEvent;
import com.moonLight.weblog.admin.event.PublishArticleEvent;
import com.moonLight.weblog.admin.event.UpdateArticleEvent;
import com.moonLight.weblog.admin.model.vo.article.*;
import com.moonLight.weblog.admin.service.AdminArticleService;
import com.moonLight.weblog.common.domain.dos.*;
import com.moonLight.weblog.common.domain.mapper.*;
import com.moonLight.weblog.common.enums.ResponseCodeEnum;
import com.moonLight.weblog.common.exception.BizException;
import com.moonLight.weblog.common.utils.PageResponse;
import com.moonLight.weblog.common.utils.Response;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
public class AdminArticleServiceImpl implements AdminArticleService {

    @Autowired
    private TArticleMapper articleMapper;  //文章
    @Autowired
    private TArticleContentMapper articleContentMapper; //文章的内容
    @Autowired
    private TArticleCategoryMapper articleCategoryRelMapper; //文章的分类
    @Autowired
    private CategoryMapper categoryMapper; //分类
    @Autowired
    private TagMapper tagMapper; //标签
    @Autowired
    private TArticleTagMapper articleTagRelMapper;
    @Autowired
    private ApplicationEventPublisher eventPublisher; //导入事件的发布

    /**
     * 发布文章
     * @param publishArticleReqVO
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class) //开启事务
    public Response publishArticle(PublishArticleReqVO publishArticleReqVO) {
        /**
         * 数据校验 已经在Vailder里面进行了校验
         * vo转do 这里只是存储了文章的一部分
         * 存储到数据库
         */
        TArticleDo tArticleDo = PublishArticleReqSettingsConvert.INSTANCE.convertPublishArticleVO2TArticleDO(publishArticleReqVO);
        articleMapper.insert(tArticleDo);

        /**
         * 因为类中被 @TableId(type = IdType.AUTO) 标注 所以插入成功后就会自动的拿到 id
         * 这是将文章的主体部分Content部分存储
         */
        Long articleId  = tArticleDo.getId();
        TArticleContentRelDo tArticleContentDo = PublishArticleReqSettingsConvert.INSTANCE.convertPublishArticleVO2TArticleContentDO(publishArticleReqVO);
        tArticleContentDo.setArticleId(articleId );
        articleContentMapper.insert(tArticleContentDo);

        /**
         * 处理文章关联的分类
         * 检查文章的分类是否存在
         * 存在 则在文章分类表中存储
         */
        Long categoryId = publishArticleReqVO.getCategoryId();
        CategoryDO categoryDO = categoryMapper.selectById(categoryId);
        if (Objects.isNull(categoryDO)) {
            log.warn("==> 分类不存在,catagoryId{}",categoryId);
            throw  new BizException(ResponseCodeEnum.CATEGORY_NOT_EXISTED);
        }
        TArticleCategoryRelDo tArticleCategoryDo = TArticleCategoryRelDo.builder()
                .articleId(articleId)
                .categoryId(categoryId)
                .build();
        articleCategoryRelMapper.insert(tArticleCategoryDo);

        /**
         * 处理文章标签 保存文章关联的标签集合
         */
        List<String> publishTags  = publishArticleReqVO.getTags();
        insertTags(publishTags,articleId);

        // 发送文章发布事件 lucene中进行对应的索引增加
        eventPublisher.publishEvent(new PublishArticleEvent(this, articleId));

        return Response.success();
    }

    /**
     * 保存标签
     * @param publishTags
     */
    private void insertTags(List<String> publishTags,long articleId) {
        /**
         * 文章的标签 可能 都需要保存  都不需要保存  部分需要
         * 需要和所有的标签比较 筛选出需要保存的标签 -> 做出分类
         */
        List<String> existsTags = new ArrayList<>();
        List<String> notExistsTags = new ArrayList<>();
        List<TagDO> tagDOS = tagMapper.selectList(null);
        // 判断数据库中标签是否存在
        if (CollectionUtils.isEmpty(tagDOS)){
           notExistsTags = publishTags;
        }else{
            //获取到数据库中的id 去筛选
            List<Long> tagsId = tagDOS.stream().map(TagDO::getId).collect(Collectors.toList());

            // 检查 publishTags 中的标签是否存在于数据库中
            for (String tag : publishTags) {
                try {
                    Long tagId = Long.parseLong(tag); // 尝试将字符串转换为Long类型
                    if (tagsId.contains(tagId)) {
                        existsTags.add(String.valueOf(tagId));
                    } else {
                        notExistsTags.add(String.valueOf(tagId));
                    }
                } catch (NumberFormatException e) {
                    // 如果转换失败，说明该标签不是数字形式的ID，直接加入notExistsTags
                    notExistsTags.add(tag);
                }
            }

            // 还有一种可能：按字符串名称提交上来的标签，也有可能是表中已存在的，比如表中已经有了 Java 标签，用户提交了个 java 小写的标签，需要内部装换为 Java 标签
            Map<String, Long> tagNameIdMap = tagDOS.stream().collect(Collectors.toMap(tagDO -> tagDO.getName().toLowerCase(), TagDO::getId));
            //比较 再次筛选掉 不存在集合中的数据   迭代器安全删除
            Iterator<String> iterator = notExistsTags.iterator();
            while (iterator.hasNext()) {
                String notExistsTag = iterator.next();
                // 转小写, 若 Map 中相同的 key，则表示该新标签是重复标签
                if (tagNameIdMap.containsKey(notExistsTag.toLowerCase())) {
                    // 从不存在的标签集合中清除
                    iterator.remove();
                    // 并将对应的 ID 添加到已存在的标签集合
                    existsTags.add(String.valueOf(tagNameIdMap.get(notExistsTag.toLowerCase())));
                }
            }
            // 将提交的上来的，已存在于表中的标签，文章-标签关联关系入库
            if (!CollectionUtils.isEmpty(existsTags)) {
                List<TArticleTagRelDo> articleTagRelDOS = Lists.newArrayList();
                existsTags.forEach(tagId -> {
                    TArticleTagRelDo articleTagRelDO = TArticleTagRelDo.builder()
                            .articleId(articleId)
                            .tagId(Long.valueOf(tagId))
                            .build();
                    articleTagRelDOS.add(articleTagRelDO);
                });
                // 批量插入
                articleTagRelMapper.insertBatchSomeColumn(articleTagRelDOS);
            }
            // 将提交的上来的，不存在于表中的标签，入库保存
            if (!CollectionUtils.isEmpty(notExistsTags)) {
                // 需要先将标签入库，拿到对应标签 ID 后，再把文章-标签关联关系入库
                List<TArticleTagRelDo> articleTagRelDOS = Lists.newArrayList();
                notExistsTags.forEach(tagName -> {
                    TagDO tagDO = TagDO.builder()
                            .name(tagName)
                            .createTime(LocalDateTime.now())
                            .updateTime(LocalDateTime.now())
                            .build();

                    tagMapper.insert(tagDO);

                    // 拿到保存的标签 ID
                    Long tagId = tagDO.getId();

                    // 文章-标签关联关系
                    TArticleTagRelDo articleTagRelDO = TArticleTagRelDo.builder()
                            .articleId(articleId)
                            .tagId(tagId)
                            .build();
                    articleTagRelDOS.add(articleTagRelDO);
                });
                // 批量插入
                articleTagRelMapper.insertBatchSomeColumn(articleTagRelDOS);
            }
        }


    }


    /**
     *  需要保证原子性 删除文章
     *  删除 t_article 中的文章记录；
     * 删除 t_article_content 中的内容；
     * 删除 t_article_category_rel 表中的文章-分类关联记录；
     * 删除 t_article_tag_rel 表中的文章-标签关联记录；
     * @param deleteArticleReqVO
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Response deleteArticle(DeleteArticleReqVO deleteArticleReqVO) {
        Long articleId = deleteArticleReqVO.getId();

        // 1. 删除文章
        articleMapper.deleteById(articleId);

        // 2. 删除文章内容
        articleContentMapper.deleteByArticleId(articleId);

        // 3. 删除文章-分类关联记录
        articleCategoryRelMapper.deleteByArticleId(articleId);

        // 4. 删除文章-标签关联记录
        articleTagRelMapper.deleteByArticleId(articleId);

        // 发布文章删除事件
        eventPublisher.publishEvent(new DeleteArticleEvent(this, articleId));

        return Response.success();
    }

    /**
     * 查询文章分页数据
     *
     * @param findArticlePageListReqVO
     * @return
     */
    @Override
    public Response findArticlePageList(FindArticlePageListReqVO findArticlePageListReqVO) {
        // 获取当前页、以及每页需要展示的数据数量
        Long current = findArticlePageListReqVO.getCurrent();
        Long size = findArticlePageListReqVO.getSize();
        String title = findArticlePageListReqVO.getTitle();
        LocalDate startDate = findArticlePageListReqVO.getStartDate();
        LocalDate endDate = findArticlePageListReqVO.getEndDate();
        Integer type = findArticlePageListReqVO.getType();


        // 执行分页查询
        Page<TArticleDo> articleDOPage = articleMapper.selectPageList(current, size, title, startDate, endDate,type);

        List<TArticleDo> articleDOS = articleDOPage.getRecords();

        // DO 转 VO
        List<FindArticlePageListRspVO> vos = null;
        if (!CollectionUtils.isEmpty(articleDOS)) {
            vos = articleDOS.stream()
                    .map(articleDO -> FindArticlePageListRspVO.builder()
                            .id(articleDO.getId())
                            .title(articleDO.getTitle())
                            .cover(articleDO.getCover())
                            .createTime(articleDO.getCreateTime())
                            .isTop(articleDO.getWeight() > 0) //是否制定
                            .build())
                    .collect(Collectors.toList());
        }

        return PageResponse.success(articleDOPage, vos);
    }

    @Override
    public Response findArticleDetail(FindArticleDetailReqVO findArticleDetailReqVO) {
        Long articleId = findArticleDetailReqVO.getId();

        TArticleDo articleDO = articleMapper.selectById(articleId);

        if (Objects.isNull(articleDO)) {
            log.warn("==> 查询的文章不存在，articleId: {}", articleId);
            throw new BizException(ResponseCodeEnum.ARTICLE_NOT_FOUND);
        }

        //对应的内容
        TArticleContentRelDo articleContentDO = articleContentMapper.selectByArticleId(articleId);

        // 所属分类
        TArticleCategoryRelDo articleCategoryRelDO = articleCategoryRelMapper.selectByArticleId(articleId);

        // 对应标签
        List<TArticleTagRelDo> articleTagRelDOS = articleTagRelMapper.selectByArticleId(articleId);
        // 获取对应标签 ID 集合
        List<Long> tagIds = articleTagRelDOS.stream().map(TArticleTagRelDo::getTagId).collect(Collectors.toList());

        // DO 转 VO
        FindArticleDetailRspVO vo = ArticleDetailConvert.INSTANCE.convertDO2VO(articleDO);
        vo.setContent(articleContentDO.getContent());
        vo.setCategoryId(articleCategoryRelDO.getCategoryId());
        vo.setTagIds(tagIds);

        return Response.success(vo);
    }

    /**
     * 注意，更新逻辑是先把对应关联数据删除掉，然后再新增新的关联记录，这样在功能实现上会更简单一些。
     * @param updateArticleReqVO
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Response updateArticle(UpdateArticleReqVO updateArticleReqVO) {
        Long articleId = updateArticleReqVO.getId();

        // 1. VO 转 ArticleDO, 并更新
        TArticleDo articleDO = TArticleDo.builder()
                .id(articleId)
                .title(updateArticleReqVO.getTitle())
                .cover(updateArticleReqVO.getCover())
                .summary(updateArticleReqVO.getSummary())
                .updateTime(LocalDateTime.now())
                .build();
        int count = articleMapper.updateById(articleDO);

        // 根据更新是否成功，来判断该文章是否存在
        if (count == 0) {
            log.warn("==> 该文章不存在, articleId: {}", articleId);
            throw new BizException(ResponseCodeEnum.ARTICLE_NOT_FOUND);
        }

        // 2. VO 转 ArticleContentDO，并更新
        TArticleContentRelDo articleContentDO = TArticleContentRelDo.builder()
                .articleId(articleId)
                .content(updateArticleReqVO.getContent())
                .build();
        articleContentMapper.updateByArticleId(articleContentDO);


        // 3. 更新文章分类
        Long categoryId = updateArticleReqVO.getCategoryId();

        // 3.1 校验提交的分类是否真实存在
        CategoryDO categoryDO = categoryMapper.selectById(categoryId);
        if (Objects.isNull(categoryDO)) {
            log.warn("==> 分类不存在, categoryId: {}", categoryId);
            throw new BizException(ResponseCodeEnum.CATEGORY_NOT_EXISTED);
        }

        // 先删除该文章关联的分类记录，再插入新的关联关系
        articleCategoryRelMapper.deleteByArticleId(articleId);
        TArticleCategoryRelDo articleCategoryRelDO = TArticleCategoryRelDo.builder()
                .articleId(articleId)
                .categoryId(categoryId)
                .build();
        articleCategoryRelMapper.insert(articleCategoryRelDO);

        // 4. 保存文章关联的标签集合
        // 先删除该文章对应的标签
        articleTagRelMapper.deleteByArticleId(articleId);
        List<String> publishTags = updateArticleReqVO.getTags();
        insertTags(publishTags,articleId);
        // 发布文章修改事件
        eventPublisher.publishEvent(new UpdateArticleEvent(this, articleId));

        return Response.success();
    }

    @Override
    public Response updateArticleIsTop(UpdateArticleIsTopReqVO updateArticleIsTopReqVO) {
        Long articleId = updateArticleIsTopReqVO.getId();
        Boolean isTop = updateArticleIsTopReqVO.getIsTop();

        // 默认权重为 0
        Integer weight = 0;
        // 若设置为置顶
        if (isTop) {
            // 查询出表中最大的权重值
            TArticleDo articleDO = articleMapper.selectMaxWeight();
            Integer maxWeight = articleDO.getWeight();
            // 最大权重值加一
            weight = maxWeight + 1;
        }

        // 更新该篇文章的权重值
        articleMapper.updateById(TArticleDo.builder()
                .id(articleId)
                .weight(weight)
                .build());

        return Response.success();
    }


}
